package com.xie.java_doc_searcher.searcher;

import com.xie.java_doc_searcher.config.Config;
import com.xie.java_doc_searcher.model.DocInfo;
import com.xie.java_doc_searcher.model.Result;
import com.xie.java_doc_searcher.model.Weight;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/**
 * Created by xiebang
 * Description 完成整个搜索的过程
 * User:小谢
 * Date:2022-07-06
 * Time:16:24
 */
public class DocSearcher {
    public static  String STOP_WORD_PATH = null;
    static {
        if (Config.isOnline){
            STOP_WORD_PATH = "/root/doc_searcher_index/stop_word.txt";
        }else {
            STOP_WORD_PATH = "D:/doc_searcher_index/stop_word.txt";
        }
    }

    // 使用 hashset 来保存停用词
    private HashSet<String> stopWords = new HashSet<>();

    // 此处要加上索引对象的实例，同时完成索引的加载
    private Index index = new Index();

    public DocSearcher(){
        index.load();
        loadStopWords();
    }
    /*
           输入：用户给出的查询词
           输出：搜索结果集合
     */
    public List<Result> search (String query){
        // 1. 分词 对 query 进行分词
        List<Term> oldTerms = ToAnalysis.parse(query).getTerms();
        List<Term> terms = new ArrayList<>();

        for (Term term : oldTerms){
            if (stopWords.contains(term.getName())){
                continue;
            }
            terms.add(term);
        }

        // 2. 触发 分词结果来查倒排

        List<List<Weight>> termResult = new ArrayList<>();
        for (Term term: terms) {
            String word = term.getName();
            // 当分词结果可能不在文档中
            List<Weight> invertedList = index.getInverted(word);
            if (invertedList == null){
                // 说明这个词在所有的文档中都不存在
                continue;
            }
            termResult.add(invertedList);
        }

        List<Weight> allTermResult = mergeResult(termResult);
        // 3. 排序 针对触发的结果按照权重降序排序
        allTermResult.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                // 降序 o2 - o1;
                // 升序 o1 - o2;
                return o2.getWeight() - o1.getWeight();
            }
        });
        // 4. 包装结果 针对排序的结果，去查正排，构造出要返回的数据
        List<Result> results = new ArrayList<>();
        for (Weight weight: allTermResult) {
            DocInfo docInfo = index.getDocInfo(weight.getDocId());
            Result result = new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
            // 描述 ： 正文的一段摘要，来自正文，同时要包含查询词或者查询词的一部分
            // 思路: 可以获取到所有的查询词的分词结果
            //  遍历分词结果，看那个结果在正文中出现
            // 当前文档不一定会包含所有的分词结果
            // 针对这个包含的分词结果，在正文中查找相应位置，就以这个词为中心，往前60 字符作为描述的开始，一般去160个字符，来作为整个描述
            result.setDesc(GenDesc(docInfo.getContent(),terms));
            results.add(result);
        }
        return results;
    }
    // 描述一个元素在二维数组中的位置
    static class Pos{
        public int row;
        public int col;

        public Pos(int row, int col) {
            this.row = row;
            this.col = col;
        }
    }
    private List<Weight> mergeResult(List<List<Weight>> source) {

        // 在进行合并的时候，是吧多个行合并成一行

        // 先针对每一行进行排序
        for (List<Weight> curRow: source) {
            curRow.sort(new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId() - o2.getDocId();
                }
            });
        }

        // 合并
        List<Weight> target = new ArrayList<>();
        PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                // 先根据 pos 值找打 Weight对象 ，在按照 docId 排序
                return source.get(o1.row).get(o1.col).getDocId() - source.get(o2.row).get(o2.col).getDocId();
            }
        });

        // 初始化队列，把每行的第一个元素放到队列中
        for(int row = 0; row < source.size(); row++){
            // 初始查的列是 0
            queue.offer(new Pos(row,0));
        }
        // 循环的去队首元素，也就是当前若干行中最小的元素

        while (!queue.isEmpty()){
            Pos minPos = queue.poll();
            Weight curWeight = source.get(minPos.row).get(minPos.col);
//            看这个取到的 Weight 是否 和 前一个插入到 target 中的结果是否 docId相同
            if(target.size() > 0){
//                取出上次插入的元素
                Weight preWeight = target.get(target.size() - 1);
                if (preWeight.getDocId() == curWeight.getDocId()){
                    preWeight.setWeight(preWeight.getWeight() + curWeight.getWeight());
                }else {
                    target.add(curWeight);
                }
            }else {
                target.add(curWeight);
            }
            // 把当前元素处理完之后，光标往后移动
            Pos newPos = new Pos(minPos.row,minPos.col + 1);
            if (newPos.col >= source.get(minPos.row).size()){
                continue;
            }
            queue.offer(newPos);

        }
        return target;
    }

    private String GenDesc(String content, List<Term> terms) {
        // 先遍历 分词结果，看看哪个结果狮子啊content 中存在
        String desc = "";
        int firstPos = -1;
        for (Term term : terms){
            String word = term.getName();
           content = content.toLowerCase().replaceAll("\\b" + word + "\\b"," " + word + " ");
            // 由于分词的时候已经把字母变成了小写，所以要把正文也小写," " +  word + " "保证独立成词 全字匹配（使用正则表达式）
            firstPos = content.indexOf(" " +  word + " ");
            if (firstPos >= 0){
                break;
            }

        }
        if (firstPos == -1){
            if (content.length() < 160){
                return content.substring(0);
            }
            return content.substring(0,160) + "...";
        }
        int descBeg = firstPos < 60 ? 0 : firstPos - 60;
        if (descBeg + 160 < content.length()){
            desc =  content.substring(descBeg,descBeg + 160) + "...";
        }else {
            desc =  content.substring(descBeg);
        }

        // 在此处加上一个 替换操作。把描述中的和分词结果相同的部分，给加上一层 <i>标签，通过replace 的方式来实现
        for (Term term:terms) {
            String word = term.getName();
            // (?i) 表示不区分大小写
            desc = desc.replaceAll("(?i) " + word + " ", "<i> " + word + " </i>");
        }
        return desc;
        // 此时搜索出来的结果包含 JS代码
    }

    public  void loadStopWords(){

        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_WORD_PATH))){
            while (true){
                String line = bufferedReader.readLine();
                if (line == null){
                    break;
                }
                stopWords.add(line);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        DocSearcher docSearcher = new DocSearcher();
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.print("-> ");
            String query = scanner.next();
            List<Result> results = docSearcher.search(query);
            for (Result result : results){
                System.out.println(result);
            }
        }

    }
}
